JTAC = {}

--[[
    需求:
    - 读取玩家的一个地图标记点，无线电菜单里面提供召唤JTAC选项
    - 允许召唤多个JTAC
    - 生成盘旋的JTAC
    - 无线电菜单里面提供选择下一个目标和发射烟雾 修改激光编码选项
    - 无线电消息（直接发全体消息）里面通报JTAC名称 目标单位名称和激光编码
]]

local version = '1.1.0'
local version_special = 'beta'

do
--- 参数配置
    local DebugMode = false -- Debug模式

    local Max_JTAC_Number_For_Player = 2 -- 玩家可同时使用的最多JTAC数量, 超出上限, 新召唤的JTAC同时会删除最早的JTAC.

    local JTAC_ORBIT_ALT = 2000 -- meters, 无人机盘旋高度 AGL.
    local JTAC_ORBIT_SPEED = 120/1.944 -- 大约120节, 无人机盘旋速度.

    local JTAC_DETECTION_RANGE = 5*1852 --m 大约5海里

    local JTAC_Spawn_On_Ground = false -- JTAC无人机是否从就近机场起飞(未实现).
    local JTAC_Inivisible = true -- JTAC无人机是否对敌人隐身.

    local JTAC_WILL_EXPIRE = true -- 是否JTAC长期未使用自动回收
    local JTAC_AUTO_EXPIRE_TIME = 1800 -- Seconds 自动回收时长(从最近一次进入睡眠状态计算)

    local MonitorRepeatTime = 1 -- Seconds 回调函数刷新速度
    local MonitorRepeatTime_Sleep = 60 -- Seconds

    local DEFAULT_SMOKE_DISAPPEAR_TIME = 300 -- 烟雾标记消失时间(游戏内默认300秒)

    local targetTypes = {
        AA = 'Air Defence',
        Vehicle = 'Ground vehicles',
        Ground_Unit = 'Ground Units',
    }

    -- Debug参数
    if DebugMode then
        MonitorRepeatTime = 1
        MonitorRepeatTime_Sleep = 10

        JTAC_WILL_EXPIRE = true
        JTAC_AUTO_EXPIRE_TIME = MonitorRepeatTime_Sleep+2
    end

--- 私有函数
    -- 获取表大小
    function getTableSize(table)
        if not table then return end

        local tableSize = 0
        for __ in pairs(table) do
            tableSize = tableSize + 1
        end

        return tableSize
    end

    -- 输出表内容
    ---@param table table to show. not a complex table though.
    ---@param level number level of this sub table.
    ---@return string formated result.
    local function tableShow(table, level)
        if type(table) ~= 'table' then return tostring(table) end
        if getTableSize(table) <= 0 then return '{}' end

        level = level or 1
        local pushBack = '  '

        local result = '{\n'
        for index,value in pairs(table) do
            for i=1,level,1 do
                result = result..pushBack
            end
            result = result..'[\"'..index..'\"] = '

            if type(value) == 'table' then
                result = result..tableShow(value,level+1)..',\n'
            end

            if type(value) == 'string' then
                result = result..'\"'..value..'\",\n'
            end

            if type(value) == 'number' then
                result = result..value..',\n'
            end

            if type(value) == 'function' then
                result = result..'[function] '..value..',\n'
            end

            if type(value) == 'boolean' then
                result = result..tostring(value)..',\n'
            end
        end

        if level > 1 then
            for i=2,level,1 do
                result = result..pushBack
            end
        end

        return result..'}'
    end

    -- 输出调试信息
    ---@param arg string|table the debug message.
    ---@param Delay number message show delay.
    local function Debug(message,Delay,forceShow,title)
        if not (DebugMode or forceShow) then return end

        if not message then message = 'false or nil.' end
        Delay = Delay or 10

        local title = title or '[Debug] '

        if type(message)=='table' then 
            message = tableShow(message)
        end
        if type(message)~='string' then message = tostring(message) end

        message = title..message
        trigger.action.outText(message,Delay)
        env.info(message)
    end

    --- 深度拷贝一个[table]类型数据
    local function deepCopy(object)
		local lookup_table = {}
		local function _copy(object)
			if type(object) ~= "table" then
				return object
			elseif lookup_table[object] then
				return lookup_table[object]
			end
			local new_table = {}
			lookup_table[object] = new_table
			for index, value in pairs(object) do
				new_table[_copy(index)] = _copy(value)
			end
			return setmetatable(new_table, getmetatable(object))
		end
		return _copy(object)
	end

    --- 将点转化为vec3
    local function makeVec3(vec,AGL)
		local result = deepCopy(vec)
        if not vec.z then
            result.z = vec.y
            result.y = 0
            if AGL then 
                result.y = land.getHeight(vec)
            end
        end

        return result
	end

    --- 获取圆形区域内随机点
    ---@param vec vec2|vec3 center point of circle zone.
    ---@param radius number radius of circle zone.
    ---@param innerRadius number smaller circle zone for not pick point from.
    ---@param maxA number in degree. Maximum degree of a sector area in clockwise.
    ---@param minA number in degree. Minimum degree of a sector area in clockwise.
    ---@return vec2 a founded random point.
    local function getRandPointInCircle(vec, radius, innerRadius, maxA, minA)
        local point = makeVec3(vec)
        local theta = 2*math.pi*math.random()
        local radius = radius or 1000
        local minR = innerRadius or 0
        if maxA and not minA then
            theta = math.rad(math.random(0, maxA - math.random()))
        elseif maxA and minA then
            if minA < maxA then
                theta = math.rad(math.random(minA, maxA) - math.random())
            else
                theta = math.rad(math.random(maxA, minA) - math.random())
            end
        end
        local rad = math.random() + math.random()
        if rad > 1 then
            rad = 2 - rad
        end

        local radMult
        if minR and minR <= radius then
            --radMult = (radius - innerRadius)*rad + innerRadius
            radMult = radius * math.sqrt((minR^2 + (radius^2 - minR^2) * math.random()) / radius^2)
        else
            radMult = radius*rad
        end

        local rndCoord
        if radius > 0 then
            rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z}
        else
            rndCoord = {x = point.x, y = point.z}
        end
        return rndCoord
    end

    --- Vector magnitude
	--- @param Vec3 vec vector
	--- @return number magnitude of vector vec
    function mag(vec)
		return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
	end

    --- Returns distance in meters between two points.
	--- @param Vec2|Vec3 point1 first point
	--- @param Vec2|Vec3 point2 second point
	--- @return number distance between given points.
	function get2DDist(point1, point2)
		point1 = makeVec3(point1)
		point2 = makeVec3(point2)
		return mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z})
	end

    --[[
        函数：decimal_to_dms
        功能：将十进制度坐标转换为度分秒格式（带方向标识）
        参数：decimal (十进制度数值), is_latitude (是否为纬度)
        返回：格式如 "N 41°28'46.44"" 的字符串
    --]]
    local function Decimal_to_dms(decimal, is_latitude)
        local degrees = math.floor(math.abs(decimal))
        local remainder = (math.abs(decimal) - degrees) * 60
        local minutes = math.floor(remainder)
        local seconds =  math.floor((remainder - minutes)*1000)

        -- 确定方向标识
        local direction
        if is_latitude then
            direction = (decimal >= 0) and "N" or "S"
        else
            direction = (decimal >= 0) and "E" or "W"
        end

        -- 格式化输出（秒保留两位小数）
        return string.format("%s %02d°%02d.%03d\"", direction, degrees, minutes, seconds)
    end

    ---@param vec3 vec3
    ---@param tType string which form you want return: 'MGRS'|'latlon' or 'LL'.
    ---@return string text of this coord.
    local function Format_Coord(vec3,tType)
        if not vec3 then return end
        if not tType then return end

        
        local oriPoint = makeVec3(vec3)
    
        if tType == 'MGRS' then
            local lat,lon,alt = coord.LOtoLL(oriPoint)
            local MGRSTable = coord.LLtoMGRS(lat,lon)

            -- return MGRSTable.UTMZone .. ' ' .. MGRSTable.MGRSDigraph .. ' ' .. MGRSTable.Easting .. ' ' .. MGRSTable.Northing
            return string.format('%03s %s %05s %05s',MGRSTable.UTMZone,MGRSTable.MGRSDigraph,MGRSTable.Easting,MGRSTable.Northing)
        end

        if tType == 'latlon' or tType == 'LL' then
            local lat,lon,alt = coord.LOtoLL(oriPoint)

            return Decimal_to_dms(lat,true)..' '..Decimal_to_dms(lon)
        end
    end

--- DCS 脚本体操
    --- Get Group.
    ---@param groupIdent string|Group Can be group name or DCS.Group.
    ---@return Group|nil
    local function getGroup(groupIdent)
        if not groupIdent then return end

        local group = groupIdent

        if type(groupIdent)=='string' then
            group = Group.getByName(groupIdent)
        end

        if not group or not group.isExist or not group:isExist() then return end
        if not group.getUnit or not group.getUnits then return end
        if not group.getController then return end
        if not group.getID then return end
        if not group.getName then return end
        if not group.getCoalition then return end

        return group
    end
    --- Get Unit.
    ---@param DCSunit Unit|string a DCS.Unit object or its name.
    ---@param alive boolean if true will check if this unit is alive.
    ---@param player boolean if true will check if this unit controlled by player.
    ---@return Unit|nil
    local function getUnit(DCSunit,alive,player)
        local unit = DCSunit
        if type(DCSunit)=='string' then
            unit = Unit.getByName(DCSunit)
        end

        if not unit then return end
        if not unit.getGroup then return end
        if not unit.getTypeName then return end
        if not unit.getID then return end
        if not unit.getCoalition then return end
        if not unit.getController then return end

        if not unit.isExist or not unit:isExist() then return end

        if alive then
            if not unit.getLife or unit:getLife()<1 then return end
        end

        if player then
            if not unit.getPlayerName or not unit:getPlayerName() then return end
        end

        return unit
    end

    --- Get unit(1).
    ---@param DCSgroup Group a DCS.Group object.
    ---@param alive boolean if true will check if this unit is alive.
    ---@param player boolean if true will check if this unit controlled by player.
    ---@return Unit|nil
    local function getFirstUnit(DCSgroup,alive,player)
        local group = getGroup(DCSgroup)
        if not group then return end

        local unit = getUnit(group:getUnit(1),alive,player)
        if not unit then return end

        return unit
    end

    --- Get Unit's group.
    ---@param DCSunit Unit|string a DCS.Unit object or its name.
    ---@return Group|nil
    local function getUnitGroup(DCSunit,alive,player)
        local unit = getUnit(DCSunit,alive,player)
        if not unit then return end

        return getGroup(unit:getGroup())
    end

    env.setErrorMessageBoxEnabled(DebugMode)

---Jtac功能实现
    --- 表
    -- 私有表
    local playerMenus = {}
    local playerJTACs = {}
    local AllJtacs = {}

    -- 公有函数
    function JTAC:New(playerUnit,pos,tCoalition)
        local playerGroup = getUnitGroup(playerUnit,true,true)
        if not playerGroup then return end

        local obj = {}

        obj.playerGroup = playerGroup
        obj.playerGroupID = playerGroup:getID()
        obj.playerGroupName = playerGroup:getName()

        local jtacGroupName = playerUnit:getPlayerName()..'-JTAC-'
        local jtacMenuName = 'JTAC-'

        local index = 1
        playerJTACs[obj.playerGroupName] = playerJTACs[obj.playerGroupName] or {}

        -- 如果玩家生成的JTAC达到上限
        if getTableSize(playerJTACs[obj.playerGroupName]) >= Max_JTAC_Number_For_Player then
            local earliestIndex = 1
            local earliest = playerJTACs[obj.playerGroupName][earliestIndex]
            for i=2,Max_JTAC_Number_For_Player,1 do
                if playerJTACs[obj.playerGroupName][i].startTime < earliest.startTime then
                    earliestIndex = i
                    earliest = playerJTACs[obj.playerGroupName][earliestIndex]
                end
            end
            playerJTACs[obj.playerGroupName][earliestIndex]:Remove()
        end

        -- 寻找空闲的index
        for i=index,Max_JTAC_Number_For_Player,1 do
            if not playerJTACs[obj.playerGroupName][i] then
                index = i
                break
            end
        end

        obj.index = index
        obj.jtacGroupName = jtacGroupName..index
        obj.jtacMenuName = jtacMenuName..index

        setmetatable(obj, self)
        self.__index = self

        obj.jtacGroup = obj:spawnJTAC(obj.jtacGroupName,pos,tCoalition)
        if not obj.jtacGroup then return end
        obj.startTime = timer.getTime()

        obj:createPlayerMenu(playerGroup,obj.jtacMenuName,obj.jtacGroup)
        
        obj:Start()

        playerJTACs[obj.playerGroupName][index] = obj
        AllJtacs[obj.jtacGroupName] = obj
        return obj
    end

    function JTAC:Remove()
        -- 删除Jtac group
        if getFirstUnit(self.jtacGroup,true) then
            Debug('移除Jtac: '..self.jtacGroupName)

            -- 删除无线电菜单 playerMenus
            missionCommands.removeItemForGroup(self.playerGroupID,playerMenus[self.playerGroupName][self.jtacGroupName])
            
            self.jtacGroup:destroy()
        end

        -- 移除激光
        self:setLaser_Off()

        -- 删除 playerJTACs
        playerJTACs[self.playerGroupName][self.index] = nil

        -- 停止Monitor.
        self:Stop()

        -- 删除 AllJtacs
        AllJtacs[self.jtacGroupName] = nil
        
        return true
    end

    -- 生成Jtac
    function JTAC:spawnJTAC(jtacGroupName,pos,tCoalition)
        local countryID = {
            [coalition.side.BLUE] = country.id.CJTF_BLUE,
            [coalition.side.RED] = country.id.CJTF_RED,
            [coalition.side.NEUTRAL] = country.id.USA,
        }

        local orbitPoint = {x=pos.x,y=pos.z}
        local orbitPoint = getRandPointInCircle(pos,2000)
        local alt = land.getHeight(orbitPoint) + JTAC_ORBIT_ALT

        local tCoalition = tCoalition or coalition.side.BLUE

        local spawnData = {}
        spawnData.name = jtacGroupName
        spawnData.task = 'Nothing'
        spawnData.units = {
            [1] = {
                ["livery_id"] = "standard",
                ["skill"] = "High",
                ["type"] = "MQ-9 Reaper",
                ["y"] = orbitPoint.y,
                ["x"] = orbitPoint.x,
                ["name"] = jtacGroupName..'-1',
            },
        }
        spawnData.route = {
            ["points"] = {
                [1] = {
                    ["alt"] = alt,
                    ["action"] = "Turning Point",
                    ["alt_type"] = "RADIO",
                    ["speed"] = JTAC_ORBIT_SPEED,
                    ["task"] = 
                    {
                        ["id"] = "ComboTask",
                        ["params"] = 
                        {
                            ["tasks"] = 
                            {
                                [1] = 
                                {
                                    ["enabled"] = true,
                                    ["auto"] = true,
                                    ["id"] = "WrappedAction",
                                    ["number"] = 1,
                                    ["params"] = 
                                    {
                                        ["action"] = 
                                        {
                                            ["id"] = "EPLRS",
                                            ["params"] = 
                                            {
                                                ["value"] = true,
                                                ["groupId"] = 1,
                                            }, -- end of ["params"]
                                        }, -- end of ["action"]
                                    }, -- end of ["params"]
                                }, -- end of [1]
                                [2] = 
                                {
                                    ["number"] = 2,
                                    ["auto"] = false,
                                    ["id"] = "WrappedAction",
                                    ["enabled"] = true,
                                    ["params"] = 
                                    {
                                        ["action"] = 
                                        {
                                            ["id"] = "SetInvisible",
                                            ["params"] = 
                                            {
                                                ["value"] = JTAC_Inivisible,
                                            }, -- end of ["params"]
                                        }, -- end of ["action"]
                                    }, -- end of ["params"]
                                }, -- end of [2]
                            }, -- end of ["tasks"]
                        }, -- end of ["params"]
                    }, -- end of ["task"]
                    ["type"] = "Turning Point",
                    ["y"] = orbitPoint.y,
                    ["x"] = orbitPoint.x,
                }, -- end of [1]
                [2] = 
                {
                    ["type"] = "Turning Point",
                    ["y"] = orbitPoint.y,
                    ["x"] = orbitPoint.x,
                    ["alt"] = alt,
                    ["action"] = "Turning Point",
                    ["alt_type"] = "RADIO",
                    ["speed"] = JTAC_ORBIT_SPEED,
                    ["task"] = 
                    {
                        ["id"] = "ComboTask",
                        ["params"] = 
                        {
                            ["tasks"] = 
                            {
                                [1] = 
                                {
                                    ["enabled"] = true,
                                    ["auto"] = false,
                                    ["id"] = "Orbit",
                                    ["number"] = 1,
                                    ["params"] = 
                                    {
                                        ["altitude"] = alt,
                                        ["pattern"] = "Circle",
                                        ["speed"] = JTAC_ORBIT_SPEED,
                                        ["altitudeEdited"] = true,
                                    }, -- end of ["params"]
                                }, -- end of [1]
                            }, -- end of ["tasks"]
                        }, -- end of ["params"]
                    }, -- end of ["task"]
                }, -- end of [2]
            }, -- end of ["points"]
        }

        local jtacGroup = coalition.addGroup(countryID[tCoalition],Group.Category.AIRPLANE,spawnData)

        if jtacGroup then
            self.orbitPoint = orbitPoint
            return jtacGroup
        end

        return
    end

    function JTAC:getTargets()
        local jtacUnit = getFirstUnit(self.jtacGroup,true)
        if not jtacUnit then
            self:Remove()
            return
        end

        Debug('JTAC:getTargets - jtacUnit: '..jtacUnit:getName())

        local controller = jtacUnit:getController()
        if not controller then return end

        local targetList = controller:getDetectedTargets(Controller.Detection.VISUAL,Controller.Detection.OPTIC,Controller.Detection.IRST)
        if getTableSize(targetList) < 1 then return end 
        
        local result = {}
        for i,targetData in pairs(targetList) do
            if targetData.object:getCategory() == Object.Category.UNIT then
                Debug('Target category: Unit.')
                if targetData.object:getDesc().category == Unit.Category.GROUND_UNIT or targetData.object:getDesc().category == Unit.Category.STRUCTURE then
                    if getUnit(targetData.object,true) then
                        local targetPoint = targetData.object:getPoint()
                        if mag(targetData.object:getVelocity()) <= 0 then
                            if get2DDist(self.orbitPoint,targetPoint) <= JTAC_DETECTION_RANGE then
                                Debug('Target unit category: Ground or structure.')
                                table.insert(result,targetData.object)
                            end
                        end
                    end
                end
            end

            -- if targetData.object:getCategory() == Object.Category.STATIC then
                
            --     local targetPoint = targetData.object:getPoint()
            --     if get2DDist(self.orbitPoint,targetPoint) <= JTAC_DETECTION_RANGE then
            --         Debug('Target category: Unit.')
            --         table.insert(result,targetData.object)
            --     end
            -- end
        end

        return result
    end

    function JTAC:setTarget()
        local targetList = self:getTargets()
        if not targetList or getTableSize(targetList)<1 then
            self:setLaser()
            return 
        end

        local priority = self.priority or targetTypes.AA

        local currentTarget = getUnit(self.currentTarget,true)
        local newTarget = nil

        -- 查找优先目标
        for i,targetUnit in pairs(targetList) do
            if targetUnit then
                if targetUnit:getDesc().attributes[priority] then
                    Debug('priority target: '..priority)
                    if not currentTarget or targetUnit:getName() ~= currentTarget:getName() then
                        newTarget = targetUnit
                        Debug('priority target found: '..newTarget:getName())
                        break
                    end
                end
            end
        end

        -- 如果没找到优先目标则返回随机目标
        if not newTarget then
            newTarget = targetList[math.random(1,#targetList)]
            if newTarget then
                Debug('random target: '..newTarget:getName())
            end
        end
        
        self.currentTarget = getUnit(newTarget,true)

        -- 照射激光
        self:setLaser()

        return self
    end
    
    function JTAC:setLaserCode(code)
        if not code or tonumber(code) > 1688 then return end

        if not self.Laser then
            self.code = code
            self:showTargetInfo()
        end

        if self.Laser then
            self.Laser:setCode(code)
            self.code = self.Laser:getCode()
        end

        self:showTargetInfo()

        return self
    end

    function JTAC:setLaser_Off()
        Debug('setLaser_Off')
        if self.Laser then  
            self.Laser:destroy()
            self.Laser = nil
        end
        if self.infraRed then 
            self.infraRed:destroy() 
            self.infraRed = nil
        end
    end

    function JTAC:setLaser()
        Debug('setLaser')
        self:setLaser_Off()

        local code = self.code or 1688

        if self.Laser then
            code = self.Laser:getCode()
        end

        local target = getUnit(self.currentTarget,true)
        if not target then
            return
        end

        local jtacGroup = getFirstUnit(self.jtacGroup,true)
        local targetPos = target:getPoint()

        self.Laser = Spot.createLaser(jtacGroup,{x=4.2,y=0,z=0}, targetPos, code)
        if self.Laser then 
            Debug('Laser on.')
            self.code = self.Laser:getCode()
        end
        
        self.infraRed = Spot.createInfraRed(jtacGroup,{x=4.2,y=0,z=0}, targetPos)
        if self.infraRed then Debug('infraRed on.') end
        
        return self
    end

    function JTAC:getTargetInfo()
        Debug('JTAC.getTargetInfo')

        local result = self.jtacGroupName..' 当前目标:'

        local target = getUnit(self.currentTarget,true)

        if target then
            local targetPoint = self.currentTarget:getPoint()
            result = result..'\n====================================='
            result = result..'\n目标类型: '..self.currentTarget:getTypeName()
            result = result..'\n坐标: MGRS: '..Format_Coord(targetPoint,'MGRS')..' | '..Format_Coord(targetPoint,'LL')
        end

        if not target then
            self:setTarget()
            if not getUnit(self.currentTarget,true) then 
                result = self.jtacGroupName..' 当前没有目标.'
            end
        end

        if self.Laser then 
            result = result..'\n激光代码: '..self.Laser:getCode()
        end

        if not self.Laser then
            if self.code then
                result = result..'\n暂未开启激光, 激光代码: '..self.code
            end
        end

        return result
    end

    function JTAC:smokeTarget()
        if not getUnit(self.currentTarget,true) then
            self:setTarget()
            if not getUnit(self.currentTarget,true) then  return end
        end

        local targetPoint = self.currentTarget:getPoint()
        local smokeColor = {
            [coalition.side.RED] = trigger.smokeColor.Red,
            [coalition.side.BLUE] = trigger.smokeColor.Blue
        }

        local tCoalition = self.playerGroup:getCoalition()

        local currentTime = timer.getTime()

        if self.smokeMarkerTime and (currentTime-self.smokeMarkerTime<DEFAULT_SMOKE_DISAPPEAR_TIME) then
            local playerUnit = getFirstUnit(self.playerGroup)
            if not playerUnit then return end

            local msg = string.format('请等待%d秒后呼叫下一次烟雾标记.',DEFAULT_SMOKE_DISAPPEAR_TIME-(currentTime-self.smokeMarkerTime))
            trigger.action.outTextForUnit(playerUnit:getID(),msg,10)
        end

        local smokePoint = makeVec3(getRandPointInCircle(targetPoint,30))
        smokePoint.y = targetPoint.y

        local smokeName = 'Smoke_'..self.currentTarget:getName()
        trigger.action.smoke(smokePoint,smokeColor[tCoalition],smokeName)
        self.smokeMarkerTime = timer.getTime()

        timer.scheduleFunction(function (smokeName)
            trigger.action.effectSmokeStop(smokeName)
        end,smokeName,timer.getTime()+DEFAULT_SMOKE_DISAPPEAR_TIME) --the smoke in game will be disappear in 5mins.

    end

    -- 生成无线电菜单
    function JTAC:createPlayerMenu(playerGroup,jtacMenuName,jtacGroup)
        if not getGroup(playerGroup) then return end
        if not getGroup(jtacGroup) then return end
        
        -- 获取玩家 group id.
        if not playerGroup then return end
        local groupID = playerGroup:getID()

        -- 获取玩家 group name.
        local playerGroupName = playerGroup:getName()
        if not playerGroupName then return end

        -- 初始化无线电表
        playerMenus[playerGroupName] = playerMenus[playerGroupName] or {}

        -- 添加无线电JTAC菜单
        local jtacMenu = missionCommands.addSubMenuForGroup(groupID,jtacMenuName)

        local targetInfoFunName = '目标信息'
        missionCommands.addCommandForGroup(groupID,targetInfoFunName,jtacMenu,self.showTargetInfo,self)

        local switchNextTargetFunName = '切换下一个目标'
        missionCommands.addCommandForGroup(groupID,switchNextTargetFunName,jtacMenu,self.switchNextTarget,self)

        local setSmokeFunName = '烟雾标记'
        missionCommands.addCommandForGroup(groupID,setSmokeFunName,jtacMenu,self.setSmoke,self)
        
        local changeLaserCodeFunName = '修改激光代码'
        local changeLaserCodeMenu = missionCommands.addSubMenuForGroup(groupID,changeLaserCodeFunName,jtacMenu)
        
        local startNum = '1' -- DCS.Spot 能接受的激光代码最大就是1688, 所以写太大也没用.
        local firstNum = missionCommands.addSubMenuForGroup(groupID,startNum,changeLaserCodeMenu)
        for s=1,6,1 do
            local secondNum = missionCommands.addSubMenuForGroup(groupID,startNum..s,firstNum)
            for t=1,8,1 do
                local thirdNum = missionCommands.addSubMenuForGroup(groupID,startNum..s..t,secondNum)
                for f=1,8,1 do
                    missionCommands.addCommandForGroup(groupID,startNum..s..t..f,thirdNum,self.changeLaserCode,{context = self, code = tonumber(startNum..s..t..f)})
                end
            end
        end

        local endJtacFunName = '结束JTAC'
        missionCommands.addCommandForGroup(groupID,endJtacFunName,jtacMenu,self.endJtac,self)

        playerMenus[playerGroupName][jtacGroup:getName()] = jtacMenu
        return self
    end

-- 无线电功能
    function JTAC.showTargetInfo(jtac)
        Debug('JTAC.showTargetInfo')

        local message = jtac:getTargetInfo()
        if message then
            trigger.action.outTextForGroup(jtac.playerGroupID,message,60,true)
        end
    end

    function JTAC.switchNextTarget(jtac)
        Debug('JTAC.switchNextTarget')
        jtac:setTarget()

        local showInfo = getUnit(jtac.currentTarget,true) or (not jtac.inSleep)
        if showInfo then
            jtac:showTargetInfo()
        end
    end

    function JTAC.setSmoke(jtac)
        Debug('JTAC.setSmoke')
        jtac:smokeTarget()
    end
    
    function JTAC.changeLaserCode(args)
        Debug('JTAC.changeLaserCode')
        Debug('new code: '..args.code)

        local jtac = args.context
        jtac:setLaserCode(args.code)
    end

    function JTAC.endJtac(jtac)
        Debug('JTAC.endJtac')
        jtac:Remove()
    end


--- 状态监控
    function JTAC._MonitorFunction(vars,time)
        local self = vars.context
        local repeatTime = vars.repeatTime
       
        if not getFirstUnit(self.playerGroup,true,true) then
            self:Remove()
        end

        if not getGroup(self.jtacGroup) or not getFirstUnit(self.jtacGroup,true) then 
            self:Remove()
        end

        -- 在目标被摧毁后切换到下一个目标
        if not getUnit(self.currentTarget,true) then
            self:switchNextTarget()
            local foundTarget = getUnit(self.currentTarget,true)

            -- 如果没有目标 进入休眠
            if not foundTarget then 
                if not self.inSleep then
                    Debug('in sleep')
                    self.inSleep = true
                    self.lastSleepTime = timer.getTime()
                end
            end

            -- 如果有目标 解除休眠
            if foundTarget then
                Debug('quit sleep')
                self.inSleep = false
            end

            if self.inSleep then
                if JTAC_WILL_EXPIRE then
                    if timer.getTime()-self.lastSleepTime >= JTAC_AUTO_EXPIRE_TIME then
                        Debug('Jtac expired due to the long-time sleep.')
                        self:Remove()
                        return
                    end
                end

                repeatTime = MonitorRepeatTime_Sleep
            end
        end

        Debug('repeatTime: '..repeatTime,repeatTime)
        return time + repeatTime
    end

    function JTAC:Start(Delay,RepeatScanSeconds)
        Debug('JTAC:Start')
        local tDelay = Delay or 1
        local RepeatScanInterval = RepeatScanSeconds or MonitorRepeatTime

        if self.MonitorID then
            self:Stop()
        end

        self.MonitorID = timer.scheduleFunction(self._MonitorFunction,{context = self,repeatTime = RepeatScanInterval},timer.getTime() + tDelay)
    end
    
    function JTAC:Stop()
        Debug('JTAC:Stop')
        if not self.MonitorID then return end

        timer.removeFunction(self.MonitorID)
        self.MonitorID = nil
    end

--- 事件监听
    -- 获取mark事件
    local markEV = {}
    function markEV:onEvent(event)
        -- Mark change 事件.
        if event.id == world.event.S_EVENT_MARK_CHANGE then
            Debug('S_EVENT_MARK_CHANGE')

            local text = event.text
            local command = string.upper(string.match(text, "^[^%s]+") or text)
            if command == 'JTAC' then
                if not event.initiator then
                    Debug('若要呼叫JTAC, 需要玩家先进入一架飞机.',10,true,'[JTAC] ')
                    return 
                end
                -- local jtacType = string.match(text, "%s([^%s]*)%s?.*")
                JTAC:New(event.initiator,event.pos,event.coalition)
                trigger.action.removeMark(event.idx)
            end
        end

        -- 玩家退出飞机 事件
        if event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then
            Debug('S_EVENT_PLAYER_LEAVE_UNIT')
            local group = getUnitGroup(event.initiator,false,true)
            if not group then return end

            -- 清除玩家生成的JTAC
            local playerGroupName = group:getName()
            if playerJTACs[playerGroupName] then
                for i,jtac in ipairs(playerJTACs[playerGroupName]) do
                    jtac:Remove()
                end
                playerJTACs[playerGroupName] = nil
            end
        end
    end
    world.addEventHandler(markEV)

end

trigger.action.outText('JTAC by Buta: version: '..version..version_special,5)